/*
 * AbstractStrategyGenerator.java
 *
 * Created on 14 de Junho de 2011, 18:44
 *
 */

package intopo.game;

import intopo.util.Util;

import cern.jet.random.engine.MersenneTwister;

import intopo.evogame.partnerselection.config.AbstractTextConfigFile;
import intopo.evogame.partnerselection.config.ParsingException;
import intopo.evogame.partnerselection.config.TagException;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.Scanner;

/**
 * An abstract class that reads data from a scanner and constructs objects
 * that have a strategy.  This scanner typically wraps an input text
 * stream.
 *
 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Mariano</a>
 *
 * @version 1.0 14 de Junho de 2011
 */
abstract public class AbstractObjectStrategyGenerator<
	S extends Strategy<S>,
	O extends AbstractObjectStrategyGenerator.Clonable<O>>
{
	/**
	 * Array containing the objects with a strategy to be returned by this
	 * generator.
	 */
	final private Object[] object;
	/**
	 * Index of the object to be returned.
	 */
	private int next;
	/**
	 * Creates a new instance of TextPlayerGenerator.  The constructor
	 * consumes the data in the scanner.
	 */
	protected AbstractObjectStrategyGenerator (Scanner scanner, Map<String, Constructor<? extends S>> map)
		throws ParsingException, Exception
	{
		int numberPlayers = AbstractTextConfigFile.readInt (scanner, "Number of players");
		int filled = 0;
		this.object = new Object [numberPlayers];
		java.util.Arrays.fill (this.object, null);
		filled = readFixedPositions (scanner, map);
		filled += readRemaining (scanner, map);
		if (filled != numberPlayers) {
			throw new Exception ("There are not enough players");
		}
		next = 0;
	}
	/**
	 * How many players are going to be generated.
	 */
	final public int size ()
	{
		return this.object.length;
	}
	/**
	 * Return the next object in this generator.
	 */
	final public O next ()
	{
		O result = (O) this.object [this.next];
		this.next++;
		return result;
	}
	/**
	 * Reads an object with a strategy from the given scanner.  An object in
	 * the stream must be described by some optional parameters, a word and
	 * strategy parameters.  The word is used to call the corresponding
	 * strategy constructor.  This constructor is retrieved from the given
	 * associative memory.
	 *
	 * @param scanner The scanner where the object is read.
	 * @param map The associative memory where strategy constructors are
	 * stored.
	 *
	 * @return An object constructed from the data in the scanner.
	 *
	 * @throws Exception If an input error occurs.
	 */
	abstract protected O readObject (Scanner scanner, Map<String, Constructor<? extends S>> map)
		throws Exception;
	/**
	 * Reads objects that are returned in fixed positions by this generator.
	 * This method reads the first section of the scanner.  This section
	 * starts with a number and is followed by zero or more object sections.
	 * The first number specifies how many object sections there are.  Each
	 * object section starts with a number that specifies the position where
	 * the object is returned.
	 *
	 * @return The number of objects created.
	 *
	 * @throws Exception If too many objects are read or if an input error
	 * occurs.
	 */
	private int readFixedPositions (Scanner scanner, Map<String, Constructor<? extends S>> map)
		throws Exception
	{
		int numberEntries = AbstractTextConfigFile.readInt (scanner, "Number of entries in fixed positions in the population");
		int filled = 0;
		while (numberEntries > 0) {
			int index = scanner.nextInt ();
			if (this.object [index] == null) {
				this.object [index] = readObject (scanner, map);
				filled++;
			}
			else {
				throw new Exception ("Position " + index + " is already taken");
			}
			numberEntries--;
		}
		return filled;
	}
	
	private int readRemaining (Scanner scanner, Map<String, Constructor<? extends S>> map)
		throws Exception
	{
		int numberEntries = AbstractTextConfigFile.readInt (scanner, "Number of entries in remaining positions in the population");
		int filled = 0;
		int index = 0;
		while (numberEntries > 0) {
			int howMany = scanner.nextInt ();
			O o = readObject (scanner, map);
			while (howMany > 0) {
				while (index < this.object.length && this.object [index] != null) {
					index++;
				}
				if (index == this.object.length) {
					throw new Exception ("Config file has too many objects specified");
				}
				this.object [index] = o;
				filled++;
				howMany--;
				index++;
				if (howMany > 0) {
					o = o.clone ();
				}
			}
			numberEntries--;
		}
		return filled;
	}
	/**
	 * Reads a strategy from the given scanner.  A strategy in the stream
	 * must be a word and parameters.  The word is used to call the
	 * corresponding strategy constructor.  This constructor is retrieved
	 * from the given associative memory.
	 *
	 * @param scanner The scanner where the object is read.
	 * @param map The associative memory where strategy constructors are
	 * stored.
	 * @param supply The random number generator that is passed to the
	 * strategy constructor.
	 *
	 * @return A strategy constructed from the data in the scanner.
	 *
	 * @throws Exception If an input error occurs.
	 */
	final protected S readStrategy (Scanner scanner, Map<String, Constructor<? extends S>> map, MersenneTwister supply)
		throws Exception
	{
		String word = AbstractTextConfigFile.readWord (scanner, "strategy constructor tag");
		Constructor<? extends S> c;
		if (map.containsKey (word)) {
			c = map.get (word);
			try {
				return c.newInstance (scanner, supply);
			}
			catch (InstantiationException e) {
				throw new Exception ("Error instanciating a strategy");
			}
		}
		else {
			throw new TagException (TagException.Entity.STRATEGY, word);
		}
	}
	
	abstract public static interface Clonable<O>
		extends Cloneable
	{
		public O clone ();
	}
}
